Popular Searches
Popular Course Categories
Popular Courses

Implementing Properties Using @property

Implementing Properties Using @property

Advanced Object-Oriented Python Concepts

Implementing Properties Using @property in Python

Course: Python Training

Topic: Implementing Properties Using @property

Course Link: Python Training Course

Demo Link: Register for a Free Course Demo

Website: JustAcademy


1. Introduction to Properties in Python

In Python, properties allow developers to control how attributes of a class are accessed, modified, and managed. The @property decorator enables a method to be accessed like an ordinary attribute without requiring parentheses.

Properties are particularly useful when implementing encapsulation, validating attribute values, calculating values dynamically, or maintaining compatibility when the internal implementation of a class changes.

For example, instead of directly accessing an internal attribute such as _price, a class can expose a property named price that controls how the value is retrieved or updated.

2. Learning Objectives

  • Understand the purpose of the @property decorator.
  • Learn how getter, setter, and deleter methods work.
  • Implement validation when modifying object attributes.
  • Understand read-only and computed properties.
  • Apply properties to real-world Python classes.
  • Identify common mistakes and follow property implementation best practices.

3. What Is the @property Decorator?

The @property decorator is a built-in Python feature that converts a method into a managed attribute. This means the method can be called through attribute access rather than a function call.

Normally, a method is called using parentheses. However, when a method is decorated with @property, its value can be accessed without parentheses.

Example: A Basic Property

class Student:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name


student = Student("Aarav")

print(student.name)

Output:

Aarav

Explanation:

  • Student is a class that represents a student.
  • _name is an internal attribute used to store the student's name.
  • @property makes the name() method accessible as student.name.
  • The method returns the stored value without exposing the internal attribute directly through the public interface.

4. Understanding Encapsulation and Properties

Encapsulation is an object-oriented programming principle that combines data and behavior within a class and controls how the object's internal state is accessed or modified.

Python does not enforce traditional private attributes in the same way as some other programming languages. Instead, naming conventions and mechanisms such as properties help establish controlled access.

Example Without a Property

class Product:
    def __init__(self, price):
        self.price = price


product = Product(500)
product.price = -100

print(product.price)

Output:

-100

This implementation allows an invalid price because no validation is performed.

Example With a Property

class Product:
    def __init__(self, price):
        self.price = price

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        if value < 0:
            raise ValueError("Price cannot be negative")
        self._price = value


product = Product(500)
print(product.price)

product.price = 700
print(product.price)

Output:

500
700

If a negative value is assigned, Python raises a ValueError. The property ensures that the object's price follows the required validation rule.

5. Getter Methods Using @property

A getter retrieves the value of an attribute. In Python, the @property decorator defines the getter method.

Syntax

class ClassName:
    @property
    def attribute_name(self):
        return self._attribute_name

Example

class Employee:
    def __init__(self, employee_id):
        self._employee_id = employee_id

    @property
    def employee_id(self):
        return self._employee_id


employee = Employee(101)
print(employee.employee_id)

Output:

101

The getter provides a controlled way to retrieve the employee ID. The caller uses employee.employee_id instead of calling a method with parentheses.

6. Setter Methods Using @property

A setter controls how an attribute is updated. It is defined using the @attribute_name.setter decorator.

Syntax

@attribute_name.setter
def attribute_name(self, value):
    # Validate or process the value
    self._attribute_name = value

Example: Validating Age

class Person:
    def __init__(self, age):
        self.age = age

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if not isinstance(value, int) or isinstance(value, bool):
            raise TypeError("Age must be an integer")
        if value < 0:
            raise ValueError("Age cannot be negative")
        self._age = value


person = Person(25)
print(person.age)

person.age = 30
print(person.age)

Output:

25
30

The setter checks the incoming value before storing it. If the value is not an integer or is negative, an appropriate exception is raised.

7. Deleter Methods Using @property

A deleter defines what happens when a property is deleted using the del statement. It is declared using the @attribute_name.deleter decorator.

Example

class Account:
    def __init__(self, account_name):
        self._account_name = account_name

    @property
    def account_name(self):
        return self._account_name

    @account_name.deleter
    def account_name(self):
        del self._account_name


account = Account("Savings")
print(account.account_name)

del account.account_name

Output before deletion:

Savings

After deletion, accessing account.account_name raises an AttributeError because the underlying attribute no longer exists.

Use deleters only when deletion is part of the intended class interface. For important business objects, an explicit method may be clearer than allowing property deletion.

8. Creating Read-Only Properties

A read-only property provides access to a value without allowing the caller to assign a new value through that property. A getter can be defined without a corresponding setter.

Example

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius


circle = Circle(5)
print(circle.radius)

Output:

5

Attempting to assign circle.radius = 10 raises an AttributeError because the property has no setter.

Important: A read-only property prevents assignment through that public property, but it does not make the underlying attribute completely private or immutable. Python code can still modify circle._radius by convention.

9. Creating Computed Properties

A computed property returns a value calculated from other attributes. It is useful when a value should always reflect the current state of an object without being stored separately.

Example: Rectangle Area

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    @property
    def area(self):
        return self.length * self.width


rectangle = Rectangle(10, 5)

print(rectangle.area)

rectangle.width = 8
print(rectangle.area)

Output:

50
80

The area is recalculated whenever the property is accessed. Updating the width automatically changes the returned area.

10. Using @property for Data Validation

Properties help maintain valid object states by checking incoming values before they are stored. This approach is useful for ages, prices, usernames, temperatures, percentages, and other attributes with specific rules.

Example: Bank Account Balance

class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    @property
    def balance(self):
        return self._balance

    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError("Balance cannot be negative")
        self._balance = value


account = BankAccount(1000)
print(account.balance)

account.balance = 1500
print(account.balance)

Output:

1000
1500

This example prevents the balance from being set to a negative value. A production banking system would also need appropriate transaction rules, authorization, concurrency handling, and other safeguards.

11. Naming Conventions for Properties

A common Python convention is to use a leading underscore for an internal attribute and expose a public property with a descriptive name.

Attribute Purpose Example
_name Internal storage attribute self._name = name
name Public property person.name
@property Defines the getter def name(self)
@name.setter Defines the setter def name(self, value)
@name.deleter Defines deletion behavior def name(self)

The leading underscore communicates that an attribute is intended for internal use. It is a convention rather than a strict access restriction.

12. Complete Example: Student Grade Management

This example combines getters, setters, validation, and a computed property in a single class.

class Student:
    def __init__(self, name, marks):
        self.name = name
        self.marks = marks

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not isinstance(value, str) or not value.strip():
            raise ValueError("Name cannot be empty")
        self._name = value.strip()

    @property
    def marks(self):
        return self._marks

    @marks.setter
    def marks(self, value):
        if not isinstance(value, (int, float)) or isinstance(value, bool):
            raise TypeError("Marks must be numeric")
        if not 0 <= value <= 100:
            raise ValueError("Marks must be between 0 and 100")
        self._marks = value

    @property
    def result(self):
        return "Pass" if self.marks >= 40 else "Fail"


student = Student("Riya", 85)

print(student.name)
print(student.marks)
print(student.result)

student.marks = 35
print(student.result)

Output:

Riya
85
Pass
Fail

Explanation:

  • The name property validates and stores a non-empty string.
  • The marks property accepts numeric values between 0 and 100.
  • The result property calculates the student's result from the current marks.
  • When marks change from 85 to 35, the computed result changes from Pass to Fail.

13. How @property Works Internally

The @property decorator creates a property descriptor. When code accesses the corresponding attribute, Python uses the descriptor protocol to determine how to retrieve, assign, or delete the value.

Consider the following code:

class Example:
    @property
    def value(self):
        return 100


obj = Example()
print(obj.value)

Python invokes the property's getter when obj.value is accessed. The caller does not need to know that a method is responsible for returning the value.

Properties also support setter and deleter methods, which define how assignment and deletion are handled. This allows a class to expose a simple attribute-style interface while controlling its implementation.

14. Difference Between Normal Attributes and Properties

Feature Normal Attribute Property
Access Directly reads stored data Can execute getter logic
Assignment Usually stores the assigned value Can validate or transform the value
Computed values Must be calculated separately or stored Can calculate values dynamically
Read-only behavior Requires additional design Possible without a setter
Interface Simple attribute access Same attribute-style access with controlled behavior
Typical use Simple data storage Validation, computed values, and controlled access

15. Difference Between @property and Getter/Setter Methods

Some programming languages commonly use explicit methods such as get_name() and set_name(). Python supports these methods, but properties often provide a more natural interface.

# Explicit getter and setter methods
class Person:
    def __init__(self, name):
        self._name = name

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value


person = Person("Aarav")
print(person.get_name())
person.set_name("Riya")

# Property-based access
class PersonWithProperty:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value


person2 = PersonWithProperty("Aarav")
print(person2.name)
person2.name = "Riya"

Properties preserve a straightforward attribute-style interface. Explicit methods remain useful when an operation represents an action, performs a complex process, or needs a descriptive command name.

16. Common Mistakes When Using @property

Mistake 1: Using the Public Property Name for Internal Storage

class Product:
    @property
    def price(self):
        return self.price

This creates recursion because accessing self.price calls the same getter repeatedly.

Correct approach:

class Product:
    def __init__(self, price):
        self._price = price

    @property
    def price(self):
        return self._price

Mistake 2: Forgetting the Setter Decorator

class Person:
    @property
    def age(self):
        return self._age

    def age(self, value):
        self._age = value

The second method replaces the first method in the class namespace. It does not define a property setter.

Correct approach:

@age.setter
def age(self, value):
    self._age = value

Mistake 3: Not Validating Values

A setter that stores every incoming value without checking it may allow invalid data into the object. Add validation when the attribute has clear requirements.

Mistake 4: Adding Properties Without a Real Need

Not every attribute requires a property. For simple values without additional behavior, ordinary attributes are often easier to read and maintain.

Mistake 5: Assuming a Property Makes Data Completely Private

A property controls access through its public interface, but a leading underscore does not prevent other code from accessing the internal attribute directly. Properties are not a complete security mechanism.

17. Best Practices for Implementing Properties

  • Use descriptive property names that represent the data being exposed.
  • Use leading underscores for internal storage attributes when appropriate.
  • Keep getters simple and predictable.
  • Validate incoming values in setters when necessary.
  • Raise appropriate exceptions such as ValueError and TypeError.
  • Use read-only properties for values that should not be assigned through the public interface.
  • Use computed properties when the result depends on other current attributes.
  • Avoid expensive operations or unexpected side effects inside getters.
  • Do not create a setter or deleter unless the class requires that behavior.
  • Write tests for valid values, invalid values, and boundary conditions.

18. Practical Applications of @property

Application Property Use Example
E-commerce Validate product prices product.price
Education Validate marks and calculate results student.result
Banking Control access to account-related values account.balance
Geometry Calculate area dynamically rectangle.area
Employee management Validate employee details employee.salary
Configuration systems Normalize or validate settings settings.timeout

19. Practice Exercises

Exercise 1: Employee Salary

Create an Employee class with a salary property. Ensure that salary cannot be negative and allow valid salary updates.

Exercise 2: Temperature Converter

Create a Temperature class that stores a temperature in Celsius and exposes a computed Fahrenheit property using the formula:

fahrenheit = (celsius * 9 / 5) + 32

Exercise 3: Rectangle Area

Create a Rectangle class with length and width attributes. Implement an area property that returns the product of the two values.

Exercise 4: Username Validation

Create a User class with a username property. Reject empty usernames and remove unnecessary whitespace before storing the value.

Exercise 5: Read-Only Identification

Create a class that generates an ID during initialization and exposes it through a read-only property.

20. Mini Project: Product Inventory Management

Objective: Build a small inventory system using properties to validate product details and calculate stock value.

Requirements

  • Create a Product class with a name, price, and quantity.
  • Use properties to retrieve and update price and quantity.
  • Reject negative prices and quantities.
  • Create a computed stock_value property.
  • Display product details and the total inventory value.

Sample Implementation

class Product:
    def __init__(self, name, price, quantity):
        self.name = name
        self.price = price
        self.quantity = quantity

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        if not isinstance(value, (int, float)) or isinstance(value, bool):
            raise TypeError("Price must be numeric")
        if value < 0:
            raise ValueError("Price cannot be negative")
        self._price = value

    @property
    def quantity(self):
        return self._quantity

    @quantity.setter
    def quantity(self, value):
        if not isinstance(value, int) or isinstance(value, bool):
            raise TypeError("Quantity must be an integer")
        if value < 0:
            raise ValueError("Quantity cannot be negative")
        self._quantity = value

    @property
    def stock_value(self):
        return self.price * self.quantity


product = Product("Keyboard", 1200, 10)

print("Product:", product.name)
print("Price:", product.price)
print("Quantity:", product.quantity)
print("Stock Value:", product.stock_value)

Output:

Product: Keyboard
Price: 1200
Quantity: 10
Stock Value: 12000

Project takeaway: The property setters enforce basic data rules, while the computed property calculates the inventory value using the current price and quantity.

21. Interview Questions and Answers

Q1. What is the @property decorator in Python?

Answer: It converts a method into a property, allowing the method to be accessed like an attribute.

Q2. What is a getter?

Answer: A getter retrieves an attribute's value. It is commonly defined using @property.

Q3. What is a setter?

Answer: A setter controls assignment to a property and can validate or transform the incoming value.

Q4. How do you define a property setter?

Answer: Use the property's name followed by .setter, as in @age.setter.

Q5. Can a property be read-only?

Answer: Yes. Define a getter without a setter. Assignment through that property will fail.

Q6. What is a computed property?

Answer: It calculates and returns a value when accessed instead of storing that result separately.

Q7. Why use an underscore in an attribute name?

Answer: A leading underscore indicates that an attribute is intended for internal use. It is a naming convention, not strict access control.

Q8. What happens when a property setter receives an invalid value?

Answer: The setter can raise an exception, such as ValueError or TypeError, to reject the value.

Q9. What is the purpose of @property.deleter functionality?

Answer: The @attribute.deleter decorator defines behavior when the property is deleted with del.

Q10. When should properties be avoided?

Answer: Avoid them when ordinary attribute access is sufficient or when the getter would perform complex, expensive, or surprising operations.

22. Quick Revision Notes

  • @property turns a method into an attribute-style getter.
  • @name.setter defines how a property is assigned.
  • @name.deleter defines how a property is deleted.
  • Properties can support validation and controlled updates.
  • Read-only properties have no setter.
  • Computed properties calculate values dynamically.
  • Internal storage commonly uses a leading underscore, such as _name.
  • Properties help maintain a clean public interface while allowing implementation details to change.
  • Use properties when they provide real value, not automatically for every attribute.

23. Final Checklist

  • Understand the difference between attributes and properties.
  • Implement getters using @property.
  • Implement setters with validation.
  • Use read-only properties where appropriate.
  • Create computed properties based on other attributes.
  • Understand how deleters work.
  • Practice implementing properties in employee, student, and inventory classes.
  • Test valid values, invalid values, and boundary conditions.

Conclusion

The @property decorator is an important Python feature for implementing encapsulation, validation, read-only access, and computed attributes. It lets developers provide a simple attribute-style interface while retaining control over how values are retrieved, updated, or deleted.

By practicing getters, setters, deleters, and computed properties, you can build Python classes that are easier to maintain, test, and extend. These concepts are especially useful in object-oriented programming and real-world application development.


Learn More with JustAcademy

Explore the Python Training Course to strengthen your Python programming and object-oriented development skills.

Interested in learning more? Register for a Course Demo to explore the training options.

Visit JustAcademy for more learning resources and courses.

whatsapp